{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "bfdc8b79",
   "metadata": {},
   "source": [
    "# L2: Build a full-stack web app"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1c7c6427",
   "metadata": {},
   "source": [
    "You are going to create a very simple web server. \n",
    "\n",
    "This server will act as a web API, taking responses from the web and replying with data."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c39e1ad4",
   "metadata": {},
   "source": [
    "## 1. Make a simple web server"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cbb160f7-6ac1-4889-8bc2-263c0dcc5ef6",
   "metadata": {
    "height": 97
   },
   "outputs": [],
   "source": [
    "const handler = (req) => {\n",
    "  // Create a new response object\n",
    "  const body = new TextEncoder().encode(\"Hello World!\");\n",
    "  return new Response(body, { status: 200 });\n",
    "};"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4bb0b955",
   "metadata": {},
   "source": [
    "### Start the server on port 8001\n",
    "\n",
    "**Note**: If ports 8000 or 8001 are unavailable, try using port 8002 or 8003."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5b8f35aa-0731-49bf-a89b-7aae0d8b1bbf",
   "metadata": {
    "height": 29
   },
   "outputs": [],
   "source": [
    "let server = Deno.serve({ port: 8001 }, handler);"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b3838da9",
   "metadata": {},
   "source": [
    "### Make a request to the server"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e17a181a-63e4-433c-87fc-e4000ee46162",
   "metadata": {
    "height": 46
   },
   "outputs": [],
   "source": [
    "const response = await fetch(\"http://localhost:8001\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "88e0f05e-0fc4-44c4-bfed-47f9d614ad7f",
   "metadata": {
    "height": 29
   },
   "outputs": [],
   "source": [
    "await response.text()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b24a5341",
   "metadata": {},
   "source": [
    "### Shut down the server"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b8bbcd57-147d-4eff-af72-bbd8b1eb73bc",
   "metadata": {
    "height": 29
   },
   "outputs": [],
   "source": [
    "await server.shutdown()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7884c922",
   "metadata": {},
   "source": [
    "### Set up the documents, index and query engine"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c049749f-cdee-4e21-901b-5fb3d2b63ba7",
   "metadata": {
    "height": 216
   },
   "outputs": [],
   "source": [
    "import * as mod from \"https://deno.land/std@0.213.0/dotenv/mod.ts\";\n",
    "import { \n",
    "    Document, \n",
    "    VectorStoreIndex, \n",
    "    SimpleDirectoryReader \n",
    "} from \"npm:llamaindex@0.1.8\"\n",
    "const keys = await mod.load({export:true}) // read API key from .env\n",
    "\n",
    "const documents = await new SimpleDirectoryReader().loadData({directoryPath: \"./data\"})\n",
    "const index = await VectorStoreIndex.fromDocuments(documents)\n",
    "const queryEngine = index.asQueryEngine()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "46e75627",
   "metadata": {},
   "source": [
    "### Create a new handler\n",
    "\n",
    "This new handler will accept a query as input and respond from the query engine."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "191a6162-2f67-4bd3-9d38-f33b4e516068",
   "metadata": {
    "height": 318
   },
   "outputs": [],
   "source": [
    "const handler2 = async (req) => {\n",
    "    if(req.method == \"POST\") {\n",
    "        // we'll expect the incoming query to be a JSON object of the form {query: ...}\n",
    "        let data = await req.json()\n",
    "        let answer = await queryEngine.query({query: data.query})\n",
    "        // and our response will be a JSON object of the form {response: ...}\n",
    "        let responseObj = {\n",
    "            response: answer.toString()\n",
    "        }\n",
    "        return new Response(JSON.stringify(responseObj), { \n",
    "            status: 200\n",
    "        })\n",
    "    } else {\n",
    "        return new Response(\"Not found\", { status: 404 })\n",
    "    }\n",
    "}\n",
    "let server2 = Deno.serve( { port: 8002 }, handler2 )"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "71a4a0aa",
   "metadata": {},
   "source": [
    "### Send a request to the server"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3e550577-b6df-4f66-85e6-1b8aa33b9641",
   "metadata": {
    "height": 29
   },
   "outputs": [],
   "source": [
    "let data = { query: \"How does the author feel about college?\" }"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cf488e1c-5879-45c0-9953-ee1a5a451f06",
   "metadata": {
    "height": 148
   },
   "outputs": [],
   "source": [
    "let response2 = await fetch(\"http://localhost:8002\", {\n",
    "  method: \"POST\",\n",
    "  headers: {\n",
    "    \"Content-Type\": \"application/json\"\n",
    "  },\n",
    "  body: JSON.stringify(data) // Convert the JavaScript object to a JSON string\n",
    "})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8713271b-a240-4832-a115-01f2c6c0a882",
   "metadata": {
    "height": 46
   },
   "outputs": [],
   "source": [
    "let responseObj = await response2.json()\n",
    "console.log(responseObj.response)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "50a9972c",
   "metadata": {},
   "source": [
    "### Shut down the server"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "287d97d6-e265-4b84-b99d-3c7970184bab",
   "metadata": {
    "height": 29
   },
   "outputs": [],
   "source": [
    "await server2.shutdown()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "408d5ca6",
   "metadata": {},
   "source": [
    "## 2. Make a web app!\n",
    "\n",
    "In this next section, we're going to use a development version of a web app, which is already setup for you.\n",
    "\n",
    "- Use the utility functions in **utils.ts** file. \n",
    "\n",
    "**Note:** Go to `File` and click on `Open` to access the **utils.ts** file and all the code used in this lesson."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "efeb989f-cc61-4076-9004-e63994979190",
   "metadata": {
    "height": 46
   },
   "outputs": [],
   "source": [
    "import { runFrontend, runBackend, addToFrontend } from './utils.ts'"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "109311e8",
   "metadata": {},
   "source": [
    "**Note**: When you run the below `runFrontend()` code in your notebook, a link will appear as the output. Click on this link to view the web app shown in the video."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6e4042e0-b895-4c38-a9e7-ebc964663e2b",
   "metadata": {
    "height": 29
   },
   "outputs": [],
   "source": [
    "runFrontend()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8e7f92f1",
   "metadata": {},
   "source": [
    "**Note**: Please note that the link generated above is specific to your notebook and will display all the upcoming frontend updates in this lesson. Please keep the page of this link open on your browser. Each time, you execute `await addToFrontend(\"\")`, you will see the new changes on that page. Note that you might need to refresh the page to see the changes."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "804f8c53",
   "metadata": {},
   "source": [
    "### Run the API server"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7a05e9cf-b9de-406c-9484-98714127917d",
   "metadata": {
    "height": 29
   },
   "outputs": [],
   "source": [
    "runBackend()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "713f3c5f",
   "metadata": {},
   "source": [
    "### Create a React component that increments a counter"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "bdc9e7ff-bce3-4378-9a45-19731a1121ef",
   "metadata": {
    "height": 233
   },
   "outputs": [],
   "source": [
    "// import-me: 1\n",
    "import React, { useState } from 'https://esm.sh/react@17.0.2';\n",
    "\n",
    "const Counter: React.FC = () => {\n",
    "  return (\n",
    "    <div>\n",
    "      <h1>Counter: 0</h1>\n",
    "      <button>Increment</button>\n",
    "    </div>\n",
    "  );\n",
    "};\n",
    "\n",
    "export default Counter;"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "52c43d35",
   "metadata": {},
   "source": [
    "### Bring it to the app\n",
    "\n",
    "This command will inject our component into a pre-written app. Check the link from the `runFrontend()` code cell and refresh the page to see the results."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "19902210-c69d-4c50-9198-7ed509caa84a",
   "metadata": {
    "height": 29
   },
   "outputs": [],
   "source": [
    "await addToFrontend(\"1\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a9914df9",
   "metadata": {},
   "source": [
    "### Add a variable"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4cefbac0-a9f0-4c2c-85ae-d4d6fd6527c0",
   "metadata": {
    "height": 267
   },
   "outputs": [],
   "source": [
    "// import-me: 2\n",
    "import React, { useState } from 'https://esm.sh/react@17.0.2';\n",
    "\n",
    "const Counter: React.FC = () => {\n",
    "  const count = 10\n",
    "\n",
    "  return (\n",
    "    <div>\n",
    "      <h1>Counter: {count}</h1>\n",
    "      <button>Increment</button>\n",
    "    </div>\n",
    "  );\n",
    "};\n",
    "\n",
    "export default Counter;"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3dde0c5c",
   "metadata": {},
   "source": [
    "Run the below to inject the new feature.\n",
    "\n",
    "**Note**: Refresh the page using the link provided by `runFrontend()` code cell to see the results."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ed0c7690-e60a-41b6-8ea2-36061c2792c0",
   "metadata": {
    "height": 29
   },
   "outputs": [],
   "source": [
    "await addToFrontend(\"2\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f1d71e6e",
   "metadata": {},
   "source": [
    "### Add interactivity"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0ed4e64b-8e42-4e6e-bcd8-86a1ebebfa51",
   "metadata": {
    "height": 352
   },
   "outputs": [],
   "source": [
    "// import-me: 3\n",
    "import React, { useState } from 'https://esm.sh/react@17.0.2';\n",
    "\n",
    "const Counter: React.FC = () => {\n",
    "  const [count, setCount] = useState<number>(0);\n",
    "\n",
    "  const increment = () => {\n",
    "    let newCount = count+1\n",
    "    setCount(newCount);\n",
    "  };\n",
    "\n",
    "  return (\n",
    "    <div>\n",
    "      <h1>Counter: {count}</h1>\n",
    "      <button onClick={increment}>Increment</button>\n",
    "    </div>\n",
    "  );\n",
    "};\n",
    "\n",
    "export default Counter;"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b27992e0",
   "metadata": {},
   "source": [
    "Inject this new version and see what happens!\n",
    "\n",
    "**Note**: Refresh the page using the link provided by `runFrontend()` code cell to view the results."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4d0477c3-f734-420a-a373-60cdf1dda900",
   "metadata": {
    "height": 29
   },
   "outputs": [],
   "source": [
    "await addToFrontend(\"3\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "728f3ddb",
   "metadata": {},
   "source": [
    "### Make an app that asks a question to the server"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b9537de2-259f-4941-b789-519e580934f9",
   "metadata": {
    "height": 301
   },
   "outputs": [],
   "source": [
    "// import-me: 4\n",
    "import React, { useState } from 'https://esm.sh/react@17.0.2';\n",
    "\n",
    "const QuerySender: React.FC = () => {\n",
    "  return (\n",
    "    <div>\n",
    "      <h1>Ask a question</h1>\n",
    "      <form>\n",
    "        <input id=\"query\" type=\"text\"/>\n",
    "        <button type=\"submit\">Query</button>\n",
    "      </form>\n",
    "      <div id=\"answer\">Answer will go here</div>\n",
    "    </div>\n",
    "  );\n",
    "};\n",
    "\n",
    "export default QuerySender;"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "472900da",
   "metadata": {},
   "source": [
    "**Note**: Refresh the page using the link provided by `runFrontend()` code cell to view the results."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "789e2ee5-de6e-4f1b-b68b-f8dea1eefc00",
   "metadata": {
    "height": 29
   },
   "outputs": [],
   "source": [
    "await addToFrontend(\"4\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8ecec6c4",
   "metadata": {},
   "source": [
    "### Add some variables"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b07a6b15-c4ff-4e54-8db9-266e091c2111",
   "metadata": {
    "height": 437
   },
   "outputs": [],
   "source": [
    "// import-me: 5\n",
    "import React, { useState } from 'https://esm.sh/react@17.0.2';\n",
    "\n",
    "const QuerySender: React.FC = () => {\n",
    "  const [query, setQuery] = useState<string>('');    \n",
    "\n",
    "  // Function to update the state with the input value\n",
    "  const handleChange = (e) => {\n",
    "    setQuery(e.target.value);\n",
    "  };\n",
    "    \n",
    "  return (\n",
    "    <div>\n",
    "      <h1>Ask a question</h1>\n",
    "      <form>\n",
    "        <input id=\"query\" type=\"text\" value={query} onChange={handleChange}/>\n",
    "        <button type=\"submit\">Query</button>\n",
    "      </form>\n",
    "      <div id=\"answer\">{query}</div>\n",
    "    </div>\n",
    "  );\n",
    "};\n",
    "\n",
    "export default QuerySender;"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1c6d5b4a",
   "metadata": {},
   "source": [
    "**Note**: Refresh the page using the link provided by `runFrontend()` code cell to view the results."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c5a5cbaa-ecb7-4163-9205-090f48917fad",
   "metadata": {
    "height": 29
   },
   "outputs": [],
   "source": [
    "await addToFrontend(\"5\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fa9fc279",
   "metadata": {},
   "source": [
    "### Add a new pair with \"answer  and setAnswer\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f7d775c3-5362-4304-91e6-d2579375cd97",
   "metadata": {
    "height": 539
   },
   "outputs": [],
   "source": [
    "// import-me: 6\n",
    "import React, { useState } from 'https://esm.sh/react@17.0.2';\n",
    "\n",
    "const QuerySender: React.FC = () => {\n",
    "  const [query, setQuery] = useState<string>('');    \n",
    "  const [answer, setAnswer] = useState<string>('');\n",
    "\n",
    "  const handleSubmit = async (e) => {\n",
    "    e.preventDefault()\n",
    "    setAnswer(query + \" is the answer\");\n",
    "  }\n",
    "    \n",
    "  // Function to update the state with the input value\n",
    "  const handleChange = (e) => {\n",
    "    setQuery(e.target.value);\n",
    "  };\n",
    "    \n",
    "  return (\n",
    "    <div>\n",
    "      <h1>Ask a question</h1>\n",
    "      <form onSubmit={handleSubmit}>\n",
    "        <input id=\"query\" type=\"text\" value={query} onChange={handleChange}/>\n",
    "        <button type=\"submit\">Query</button>\n",
    "      </form>\n",
    "      <div id=\"answer\">{answer}</div>\n",
    "    </div>\n",
    "  );\n",
    "};\n",
    "\n",
    "export default QuerySender;"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "21fc0a87",
   "metadata": {},
   "source": [
    "**Note**: Refresh the page using the link provided by `runFrontend()` code cell to view the results."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ed543735-e892-4157-a078-abd62a623470",
   "metadata": {
    "height": 29
   },
   "outputs": [],
   "source": [
    "await addToFrontend(\"6\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fbd9ed94",
   "metadata": {},
   "source": [
    "### Call the back-end"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "26b5048e-274c-4ea8-9f51-777fbc384bf0",
   "metadata": {
    "height": 726
   },
   "outputs": [],
   "source": [
    "// import-me: 7\n",
    "import React, { useState } from 'https://esm.sh/react@17.0.2';\n",
    "\n",
    "const QuerySender: React.FC = () => {\n",
    "  const [query, setQuery] = useState<string>('');\n",
    "  const [answer, setAnswer] = useState<string>('');\n",
    "\n",
    "  const handleSubmit = async (e) => {\n",
    "    e.preventDefault()\n",
    "    setAnswer(\"Thinking...\")\n",
    "    const response = await fetch('http://localhost:8000', {\n",
    "      method: 'POST',\n",
    "      headers: {\n",
    "        'Content-Type': 'application/json',\n",
    "      },\n",
    "      body: JSON.stringify({ query }),\n",
    "    });\n",
    "    const data = await response.json();\n",
    "    console.log('Response from the server:', data);\n",
    "    setAnswer(data.response);\n",
    "  }\n",
    "\n",
    "  // Function to update the state with the input value\n",
    "  const handleChange = (e) => {\n",
    "    setQuery(e.target.value);\n",
    "  };  \n",
    "\n",
    "  return (\n",
    "    <div>\n",
    "      <h1>Ask a question</h1>\n",
    "      <form onSubmit={handleSubmit}>\n",
    "        <input id=\"query\" type=\"text\" value={query} onChange={handleChange} />\n",
    "        <button type=\"submit\">Query</button>\n",
    "      </form>\n",
    "      <div id=\"answer\">{answer}</div>\n",
    "    </div>\n",
    "  );\n",
    "};\n",
    "\n",
    "export default QuerySender;\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8405cc7e",
   "metadata": {},
   "source": [
    "**Note**: Refresh the page using the link provided by `runFrontend()` code cell to view the results."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3bd685f5-c571-4c52-be98-5bcd48745c8a",
   "metadata": {
    "height": 29
   },
   "outputs": [],
   "source": [
    "await addToFrontend(\"7\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cb9619be",
   "metadata": {},
   "source": [
    "### Congratulations, you've made a web app!\n",
    "\n",
    "Hurrah! You have written a React component that calls out to a server, makes a request, and updates the UI with the answer. "
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Deno",
   "language": "typescript",
   "name": "deno"
  },
  "language_info": {
   "file_extension": ".ts",
   "mimetype": "text/x.typescript",
   "name": "typescript",
   "nb_converter": "script",
   "pygments_lexer": "typescript",
   "version": "5.3.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
